// SlSession.cs
//	
// Author:
//   [NAME] [EMAIL]
//
// Copyright (c) 2008, [OWNER]
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
//    * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//

using System;
using System.Diagnostics;
using System.Threading;
using System.Reflection;
using OpenMetaverse;
using OpenMetaverse.Packets;
using System.Collections.Generic;
using log4net;
using log4net.Config;

namespace OpenFuse.Backend
{
	public class SlSession : OpenFuse.Backend.ManageSimConnection, IDisposable
	{
		private GridClient _gridClient;
		private UUID _avatarId;
		private ManualResetEvent _commandInQueueEvent = new ManualResetEvent(false);
		private Thread _commandThread = null;
		private volatile bool _terminateCommandThreadFlag = false;
		PriorityQueue<Commands.Command, int> _commandQueue = new PriorityQueue<Commands.Command, int>();

		private static readonly ILog _logger = LogManager.GetLogger("Backend");
		private Cache.LocalCache _cache;
		
		public UUID WorkFolderID {get; private set;}
		public string GridName { get; private set;}
		public string FirstName { get; private set; }		
		public string LastName { get; private set; }
		public string FullName { get { return FirstName+"."+LastName;} }
		public Cache.LocalCache Cache { get { return _cache;} }
		
		public GridClient GridClient{ get { return _gridClient; } }
		internal ILog Logger { get { return _wrappedLogger; } }
		private SessionLogger _wrappedLogger;				// make sure that all logs are marked with the
															// relevant account info

		public SlSession(Cache.LocalCache cache, string gridName, string firstName, string lastName)
		{
			_cache = cache;
			GridName = gridName;
			FirstName = firstName;
			LastName = lastName;

			_wrappedLogger = new SessionLogger(this, _logger);
			
			OpenMetaverse.Settings.RESOURCE_DIR = "data";

			_gridClient = new GridClient();
			_gridClient.Network.SimConnected += new EventHandler<SimConnectedEventArgs>(OnSimConnected);
//            _gridClient.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(OnAgentDataUpdate));
			_gridClient.Network.Disconnected += new EventHandler<DisconnectedEventArgs>( OnGridDisconnected );
			_gridClient.Network.SimDisconnected += new EventHandler<SimDisconnectedEventArgs>( OnSimDisconnected );
			_gridClient.Network.LoggedOut += new EventHandler<LoggedOutEventArgs>( OnLogoutReply );
			_gridClient.Self.AlertMessage += new EventHandler<AlertMessageEventArgs>( OnAlertMessageCallback );
			// Turn off some unnecessary things
			_gridClient.Settings.MULTIPLE_SIMS = false;
			
			// Throttle packets that we don't want all the way down
			_gridClient.Throttle.Land = 0;
			_gridClient.Throttle.Wind = 0;
			_gridClient.Throttle.Cloud = 0;

			_gridClient.Settings.ENABLE_CAPS = true;
			_gridClient.Settings.USE_ASSET_CACHE = true;		// without this flag, OpenMetaverse borks texture downloads
			_gridClient.Settings.MULTIPLE_SIMS = false;			// we don't need to look into neighboring sims
			_gridClient.Settings.OBJECT_TRACKING = true;
			
			_commandThread = new Thread(new ThreadStart(CommandQueueRun));
			_commandThread.Name = "Downloader [" +GridName+ ":" +FirstName+ " " +LastName+ "]";
			_commandThread.Start();
		}

		public void StartReplication()
		{
			AddCommand(new Backend.Commands.GetTopFoldersCommand(this, new Backend.Commands.GetTopFoldersCommand.Result(_cache.InsertNodes)));
		}
		
		#region Command pattern management
		void CommandQueueRun()
		{
			Commands.Command cmd = null;
			while(!_terminateCommandThreadFlag)
			{
				_commandInQueueEvent.WaitOne();
				lock(_commandQueue)
				{
					cmd = _commandQueue.Dequeue().Value;
					if(_commandQueue.Count == 0)
					   _commandInQueueEvent.Reset();
				}
				try{
					cmd.Run();
				}
				catch(Exception e)
				{
					Logger.Error("Command runner caught:", e);
				}
			}
		}

		public void FlushQueue()
		{
			lock(_commandQueue)
			{
				_commandQueue.Clear();
				_commandInQueueEvent.Reset();
			}
		}
		
		
		public void AddCommand(Commands.Command cmd)
		{
			AddCommand(cmd, Commands.Command.MaxCommandPriority);
		}
		
		public void AddCommand(Commands.Command cmd, int priority)
		{
			cmd.Priority = priority;
			lock(_commandQueue)
			{
				_commandQueue.Enqueue(cmd, priority);
				_commandInQueueEvent.Set();
			}
		}

		public bool IsCommandQueueEmpty { get { return _commandQueue.Count == 0; } }
		public int CommandQueueSize { get { return _commandQueue.Count; } }
		
		#endregion Command pattern management

		internal bool TerminateCommandThread
		{
			get { return _terminateCommandThreadFlag; }
			set { _terminateCommandThreadFlag = value; }
		}
		
		public UUID AvatarId { get { return _avatarId; } }
		
		public void UploadItem(Guid id)
		{
			Cache.Asset asset = _cache.Assets.GetByNodeId(id);
			if(asset != null)
				AddCommand(new Backend.Commands.UploadObjectCommand(this, asset));
			else
				Logger.ErrorFormat("Failed to load {0} from cache", id);
		}

		public bool Login(string password, string loginUrl)
		{
			return Backend.Utilities.Login(this, FirstName, LastName, password, loginUrl);
		}
		
		#region LibOpenMetaverse events
		private void OnAlertMessageCallback(object o, AlertMessageEventArgs e)
		{
			Logger.Info("Alert: " + e.Message);
		}
		
		private void OnSimConnected(object o, SimConnectedEventArgs args)
		{
			Logger.DebugFormat("Connected to Sim: '{0}", args.Simulator.Name);

			UUID trashFolderUUID = _gridClient.Inventory.FindFolderForType(OpenMetaverse.AssetType.TrashFolder);
			WorkFolderID = Backend.Utilities.CreateFolder(this, trashFolderUUID, "OpenFuseWorkFolder");
			_avatarId = args.Simulator.Client.Self.AgentID;
		}

/*
 * 		private void OnAgentDataUpdate(Packet packet, Simulator sim)
        {
            AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet;
            if (p.AgentData.AgentID == sim.Client.Self.AgentID)
            {
                Logger.Debug("Got the group ID for " + sim.Client.ToString() + ", requesting group members...");
                UUID groupID = p.AgentData.ActiveGroupID;

                sim.Client.Groups.RequestGroupMembers(groupID);
            }
        }
*/

		/// <summary>
		/// An event for being logged out either through client request, server forced, or network error
		/// </summary>
		void OnGridDisconnected( object src, OpenMetaverse.DisconnectedEventArgs args)
		{
			if(args.Reason == NetworkManager.DisconnectType.NetworkTimeout)
			{
				Logger.Error("Network connection timed out, disconnected:" +args.Message);
			}
			else if(args.Reason == NetworkManager.DisconnectType.ServerInitiated)
			{
				Logger.Error("Server disconnected us: " + args.Message);
			}
			else if(args.Reason == NetworkManager.DisconnectType.SimShutdown)
			{
				Logger.Error("The sim was shutting down: " + args.Message);
			}
		}

		/// <summary>
		/// An event for the connection to a simulator other than the currently occupied one disconnecting
		/// </summary>
		void OnSimDisconnected( object src, SimDisconnectedEventArgs args)
		{
			if(args.Reason == NetworkManager.DisconnectType.NetworkTimeout)
			{
				Logger.Error("Network connection timed out, disconnected:" + args.Simulator.Name);
			}
			else if(args.Reason == NetworkManager.DisconnectType.ServerInitiated)
			{
				Logger.Error("Server disconnected us: " + args.Simulator.Name);
			}
			else if(args.Reason == NetworkManager.DisconnectType.SimShutdown)
			{
				Logger.Error("The sim was shutting down: " + args.Simulator.Name);
			}
		}
 
		/// <summary>
		/// Event raised when a logout is confirmed by the simulator
		/// </summary>
		void OnLogoutReply ( object src, LoggedOutEventArgs args )
		{
			Console.WriteLine( "Network_OnLogoutReply" );
 
//			foreach ( UUID item in args.InventoryItems )
//			{
//				Console.WriteLine( "\tInventory Item: " + item.ToString() );
//			}
//			Console.WriteLine( "" );
		}
		#endregion LibOpenMetaverse events

		#region IDisposable implementation
		private bool disposed = false;

		
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		
		protected virtual void Dispose(bool disposing)
		{
			if(!disposed)
			{
				if(disposing)
				{
					// Dispose managed resources.
				}

				if(_commandThread != null)
				{
					AddCommand(new Commands.TerminateCommand(this));
//					_terminateCommandThreadFlag = true;
					Logger.Debug("Threadstate " + Enum.GetName(typeof(System.Threading.ThreadState), _commandThread.ThreadState));
					while(_commandThread.ThreadState != System.Threading.ThreadState.Stopped)
					{
						Logger.DebugFormat("Waiting for {0} outstanding commands", _commandQueue.Count);
						_commandThread.Join(1000);
						Logger.Debug("Threadstate " + Enum.GetName(typeof(System.Threading.ThreadState), _commandThread.ThreadState));
					}
					_commandThread = null;
				}
				// unmanaged resources need to be released here.
				if(_gridClient.Network.Connected)
					_gridClient.Network.RequestLogout();
			}
			Logger.Debug("Disposed");
			disposed = true;
		}
		#endregion

	}
}
